using System.Runtime.InteropServices;
using Engine.Media;
using Silk.NET.OpenAL;

namespace Engine.Audio
{
	public class SoundBuffer : IDisposable
	{
        public int m_buffer;

		public int ChannelsCount
		{
			get;
			set;
		}

		public int SamplingFrequency
		{
			get;
			set;
		}

		public int SamplesCount
		{
			get;
			set;
		}

		public int UseCount
		{
			get;
			set;
		}

		public SoundBuffer(byte[] data, int startIndex, int itemsCount, int channelsCount, int samplingFrequency)
		{
			Initialize(data, startIndex, itemsCount, channelsCount, samplingFrequency);
			CreateBuffer(data, startIndex, itemsCount, channelsCount, samplingFrequency);
		}

		public SoundBuffer(short[] data, int startIndex, int itemsCount, int channelsCount, int samplingFrequency)
		{
			Initialize(data, startIndex, itemsCount, channelsCount, samplingFrequency);
			CreateBuffer(data, startIndex, itemsCount, channelsCount, samplingFrequency);
		}

		public SoundBuffer(Stream stream, int bytesCount, int channelsCount, int samplingFrequency)
		{
			byte[] array = Initialize(stream, bytesCount, channelsCount, samplingFrequency);
			CreateBuffer(array, 0, array.Length, channelsCount, samplingFrequency);
		}
		public SoundBuffer() { }
		private void InternalDispose()
		{
			if (m_buffer != 0)
			{
				Mixer.AL.DeleteBuffer((uint)m_buffer);
				Mixer.CheckALError();
				m_buffer = 0;
			}
		}

		private unsafe void CreateBuffer<T>(T[] data, int startIndex, int itemsCount, int channelsCount, int samplingFrequency)
		{
			uint buffer = Mixer.AL.GenBuffer();
            m_buffer = (int)buffer;
			Mixer.CheckALError();
			GCHandle gCHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
			try
			{
				int num = Utilities.SizeOf<T>();
                Mixer.AL.BufferData(buffer, (channelsCount == 1) ? BufferFormat.Mono16 : BufferFormat.Stereo16, (gCHandle.AddrOfPinnedObject() + (startIndex * num)).ToPointer(), itemsCount * num, samplingFrequency);
				Mixer.CheckALError();
			}
			finally
			{
				gCHandle.Free();
			}
		}


		public void Dispose()
		{
			if (UseCount != 0)
			{
                throw new InvalidOperationException("无法处置正在使用的SoundBuffer");
			}
			InternalDispose();
		}

		public static SoundBuffer Load(SoundData soundData)
		{
			return new SoundBuffer(soundData.Data, 0, soundData.Data.Length, soundData.ChannelsCount, soundData.SamplingFrequency);
		}

		public static SoundBuffer Load(Stream stream, SoundFileFormat format)
		{
			return Load(SoundData.Load(stream, format));
		}

		public static SoundBuffer Load(string fileName, SoundFileFormat format)
		{
			return Load(SoundData.Load(fileName, format));
		}

		public static SoundBuffer Load(Stream stream)
		{
			return Load(SoundData.Load(stream));
		}

		public static SoundBuffer Load(string fileName)
		{
			return Load(SoundData.Load(fileName));
		}

        public void InitializeProperties(int samplesCount, int channelsCount, int samplingFrequency)
		{
			if (samplesCount <= 0)
			{
				throw new InvalidOperationException("Buffer cannot have zero samples.");
			}
			if (channelsCount < 1 || channelsCount > 2)
			{
				throw new ArgumentOutOfRangeException(nameof(channelsCount));
			}
			if (samplingFrequency < 8000 || samplingFrequency > 192000)
			{
				throw new ArgumentOutOfRangeException(nameof(samplingFrequency));
			}
			ChannelsCount = channelsCount;
			SamplingFrequency = samplingFrequency;
			SamplesCount = samplesCount;
		}

		private void Initialize<T>(T[] data, int startIndex, int itemsCount, int channelsCount, int samplingFrequency)
		{
			int num = Utilities.SizeOf<T>();
			InitializeProperties(itemsCount * num / channelsCount / 2, channelsCount, samplingFrequency);
			ArgumentNullException.ThrowIfNull(data);
			if (startIndex + itemsCount > data.Length)
			{
				throw new ArgumentOutOfRangeException(nameof(itemsCount));
			}
		}

		private byte[] Initialize(Stream stream, int bytesCount, int channelsCount, int samplingFrequency)
		{
			byte[] array = new byte[bytesCount];
			if (stream.Read(array, 0, bytesCount) != bytesCount)
			{
				throw new InvalidOperationException("Not enough data in stream.");
			}
			Initialize(array, 0, bytesCount, channelsCount, samplingFrequency);
			return array;
		}
	}
}